﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;

namespace SimplexEngine
{
    public enum AnimationLoopMode
    { 
        ONCE,
        LOOP
    }

    public class Animation
    {
        public virtual float Length { get { return 0.0f; } }
        public virtual AnimationLoopMode LoopMode { get { return loop_mode; } set { loop_mode = value; } }

        public virtual void UpdateMeshBuffer(float time, AnimationMeshInstance mesh_instance, ref Dictionary<String, MeshBuffer> mesh_buffers) {  }
 
        protected void GetDeltaAndFrame(float time, out float delta, List<AnimationKeyFrame> frames, out AnimationKeyFrame frame1, out AnimationKeyFrame frame2)
        {
            if (time > Length)
            {
                delta = 0.0f;
                frame1 = frames.Last();
                frame2 = frames.Last();
                return;
            }

            // TODO : use binary search
            for (int i = 0; i < frames.Count; ++i)
            {
                if (time < frames[i].Time)
                {
                    frame1 = frames[i - 1];
                    frame2 = frames[i];
                    delta = (time - frame1.Time) / (frame2.Time - frame1.Time);
                    return;
                }
            }
            
            delta = 0.0f;
            frame1 = null;
            frame2 = null;
        }


        protected AnimationLoopMode loop_mode = AnimationLoopMode.ONCE;
    }

    public enum AnimationType
    { 
        VERTEX,
        BONE
    }

    public class AnimationKeyFrame
    {
        public float Time = 0.0f;
    }

    public class AnimationMesh : StaticMesh
    {
        public static AnimationMeshInstance CreateAnimationMeshInstance(Resource animation_mesh)
        {
            return new AnimationMeshInstance(animation_mesh);
        }

        public virtual Dictionary<String, Animation> Animations { get { return animations; } }
        protected Dictionary<String, Animation> animations = new Dictionary<String, Animation>();

        public AnimationType AnimationType { get { return animation_type; } set { animation_type = value; } }
        protected AnimationType animation_type = AnimationType.VERTEX;
    }

    public class AnimationMeshInstance : StaticMeshInstance
    {
        public AnimationMeshInstance(Resource source) : base(source)
        {
        }

        public String ActiveAnimation
        {
            get { return active_animation_name; }
            set
            {
                active_animation_name = value;
                must_set_loop_mode = true;
            }
        }

        public virtual void OnUpdate(GameTime game_time, Int64 frame_number)
        {
            CurrentTime += speed * (float)game_time.ElapsedGameTime.TotalSeconds;
        }

        public float Speed { get { return speed; } set { speed = value; } }
        protected float speed = 1.0f;
        
        public virtual AnimationLoopMode LoopMode { get { return loop_mode; } set { loop_mode = value; must_set_loop_mode = false; } }
        protected AnimationLoopMode loop_mode = AnimationLoopMode.ONCE;

        protected virtual void CreateVertexAnimationRenderElements(SceneObject scene_object, Animation animation, ref List<RenderElement> render_elemets)
        {

            animation.UpdateMeshBuffer(current_time, this, ref mesh_buffers);

            foreach (KeyValuePair<String, Material> material in materials)
            {
                if (material.Value.Effect != null)
                {
                    MeshBuffer mesh_buffer = mesh_buffers[material.Key];
                    render_elemets.Add(new RenderElement(mesh_buffer, material.Value, scene_object));
                }
            }
        }

        protected virtual void CreateBoneAnimationRenderElements(SceneObject scene_object, Animation animation, ref List<RenderElement> render_elemets)
        {
            foreach (RenderElement render_element in base.CreateRenderElements(scene_object))
                render_elemets.Add(render_element);
        }

        public override List<RenderElement> CreateRenderElements(SceneObject scene_object)
        {
            List<RenderElement> result = new List<RenderElement>();

            AnimationMesh mesh = mesh_resource.GetObject<AnimationMesh>();

            if (mesh != null)
            {
                if (materials == null)
                    BuildMaterials(mesh);

                Animation animation = null;

                if (mesh.Animations.TryGetValue(active_animation_name, out animation))
                {
                    if (must_set_loop_mode)
                        LoopMode = animation.LoopMode;

                    if (loop_mode == AnimationLoopMode.LOOP)
                    {
                        while (current_time > animation.Length)
                            current_time -= animation.Length;
                    }

                    if (mesh.AnimationType == AnimationType.VERTEX)
                    {
                        CreateVertexAnimationRenderElements(scene_object, animation, ref result);
                    }
                    else if (mesh.AnimationType == AnimationType.BONE)
                    {
                        CreateBoneAnimationRenderElements(scene_object, animation, ref result);
                    }
                }
                else
                {
                    return base.CreateRenderElements(scene_object);
                }
            }

            return result;
        }

        protected override void BuildMaterials(StaticMesh mesh)
        {
            base.BuildMaterials(mesh);

            AnimationMesh animation_mesh = mesh as AnimationMesh;

            foreach (KeyValuePair<String, StaticMeshPart> mesh_part in mesh.MeshParts)
            {
                Int32[] indices = new Int32[mesh_part.Value.MeshBuffer.GetIndexCount()];
                Vertex[] vertices = new Vertex[mesh_part.Value.MeshBuffer.GetVertexCount()];

                mesh_part.Value.MeshBuffer.GetIndices().CopyTo(indices, 0);
                mesh_part.Value.MeshBuffer.GetVertices().CopyTo(vertices, 0);

                MeshBuffer mesh_buffer = new MeshBuffer();

                mesh_buffer.SetIndices(indices);
                mesh_buffer.SetVertices(vertices);

                mesh_buffers.Add(mesh_part.Key, mesh_buffer);
            }
        }

        protected Dictionary<String, MeshBuffer> mesh_buffers = new Dictionary<String, MeshBuffer>();

        protected String active_animation_name = "";

        protected Boolean must_set_loop_mode = false;

        protected float current_time = 0.0f;
        public float CurrentTime { get { return current_time; } set { current_time = value; } }
    }
}
